控制器提供了对应用程序行为(通常是service接口定义)的访问。控制器拦截了用户的输入,将它转成一个传递给用户的试图模型。Spring用非常抽象的方式实现了控制器,使你可以创建各种各样的控制器。
Spring2.5为MVC控制器引入了基于注解的编程模型,比如@RequestMapping@RequestParam@ModelAttribute等等。这些注解支持Servlet MVC和Portlet MVC。通过这种方式实现的控制器不需要继承特定的基类或是实现特定的接口。另外,它们通常也不会直接依赖Servlet或是Protlet API,尽管你可以通过简单的配置来访问Servlet或Prolete功能。

在Github上pring-project项目中,许多web应用程序都利用了这节中讨论的注解的支持,它们包括MvcShowcase,MvcAjax,MvcBasic,PetClinic,PetCare等。

@Controller
public class HelloWorldController {

    @RequestMapping("/helloWorld")
    public String helloWorld(Model model) {
        model.addAttribute("message", "Hello World!");
        return "helloWorld";
    }
}

正如你所看到的,@Controller@RequestMapping注解允许灵活的方法名称和签名。在这个特殊的例子中,方法接受了Model并返回了一个String作为视图的名字,但是本章之后会介绍其他方法参数和返回值也可以被使用。@Controller@RequestMapping和其他一些注解为Spring MVC的实现提供了基础。本节是关于介绍这些注解的文档以及他们通常如何在Servlet环境使用。

22.3.1 Defining a controller with @Controller

@Controller注解表示这个特殊类的扮演了控制器的角色。Spring并没有要求你的控制器类需要拓展自某个基类或是引用Servlet API。但是,如果你需要的话你仍可以引用Servlet的特性。
@Controller注解作为注解的类的标记,指明了类的角色。派发器扫描这些被注解的类,检测其方法上的@RequestMapping注解(见下一节),然后映射这些方法。
你可以在派发器的上下文中通过标准的Spring bean定义来显式的声明一个被注解的控制器bean。但是,@Controller标记也允许自动检测,这和Spring对类路径下一般的component类进行扫描和注册是一样的。
为了启用对被注解的控制器的自定检测,你需要在你的配置中添加组建扫描。像下面的XML这样使用spring-context schema:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="org.springframework.samples.petclinic.web"/>

    <!-- ... -->

</beans>

22.3.2 Mapping Reqeusts With @RequestMapping

你可以使用@ReqeustMapping注解将诸如/appointment的URL映射到整个类或是特殊的处理器方法上。通常,类级别的注解将特定的请求路径(或路径样式)映射到控制器上,在通过方法级别的注解将映射缩小到特定的HTTP方法请求上("GET","POST"等)或是HTTP请求参数条件上。
下面的例子来自PetCare,展示了Spring MVC中控制器如何使用注解:

@Controller
@RequestMapping("/appointments")
public class AppointmentsController {

    private final AppointmentBook appointmentBook;

    @Autowired
    public AppointmentsController(AppointmentBook appointmentBook) {
        this.appointmentBook = appointmentBook;
    }

    @RequestMapping(method = RequestMethod.GET)
    public Map<String, Appointment> get() {
        return appointmentBook.getAppointmentsForToday();
    }

    @RequestMapping(path = "/{day}", method = RequestMethod.GET)
    public Map<String, Appointment> getForDay(@PathVariable @DateTimeFormat(iso=ISO.DATE) Date day, Model model) {
        return appointmentBook.getAppointmentsForDay(day);
    }

    @RequestMapping(path = "/new", method = RequestMethod.GET)
    public AppointmentForm getNewForm() {
        return new AppointmentForm();
    }

    @RequestMapping(method = RequestMethod.POST)
    public String add(@Valid AppointmentForm appointment, BindingResult result) {
        if (result.hasErrors()) {
            return "appointments/new";
        }
        appointmentBook.addAppointment(appointment);
        return "redirect:/appointments";
    }
}

这个例子好几处都用到了@RequestMapping。第一处是在类型(类)级别,表明了这个控制器中的所有处理器方法都和/appointments路径相关。get()方法还用@ReqeustMapping进行了细化:它只接受GET请求,意味着/appointmentsGET请求会调用它。add()方法有个相似的喜欢,getNewForm()将HTTP方法和路径结合到了一起,因此appointments/newGET请求会被这个方法处理。
getForDay方法展示了@RequestMapping的另一个用法:URI模板。(见"URI Tempate Patterns")。
类级别的@RequestMapping不是必要的。没有它,所有的路径都表示简单的绝对路径,而非相对路径。下面饿例子来自PetClinic:

@Controller
public class ClinicController {

    private final Clinic clinic;

    @Autowired
    public ClinicController(Clinic clinic) {
        this.clinic = clinic;
    }

    @RequestMapping("/")
    public void welcomeHandler() {
    }

    @RequestMapping("/vets")
    public ModelMap vetsHandler() {
        return new ModelMap(this.clinic.getVets());
    }

}

上面的例子没有指定GETPUTPOST等,因为@RequestMapping默认映射了所有的HTTP方法。用@RequestMapping(method=GET)或是@GetMapping可以细化映射。

Composed @RequestMapping Variants

Spring 4.3引入了方法级别的@RequestMapping注解的变体,来帮助简化对通用HTTP方法的映射和更好的表达被注解方法的语义。比如,@GetMapping可以被理解为一个GET@RequestMapping
@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
* PatchMapping
下面的例子是前一节的AppointmentsController的修改版本,使用了联合的@RequestMapping注解。

@Controller
@RequestMapping("/appointments")
public class AppointmentsController {

    private final AppointmentBook appointmentBook;

    @Autowired
    public AppointmentsController(AppointmentBook appointmentBook) {
        this.appointmentBook = appointmentBook;
    }

    @GetMapping
    public Map<String, Appointment> get() {
        return appointmentBook.getAppointmentsForToday();
    }

    @GetMapping("/{day}")
    public Map<String, Appointment> getForDay(@PathVariable @DateTimeFormat(iso=ISO.DATE) Date day, Model model) {
        return appointmentBook.getAppointmentsForDay(day);
    }

    @GetMapping("/new")
    public AppointmentForm getNewForm() {
        return new AppointmentForm();
    }

    @PostMapping
    public String add(@Valid AppointmentForm appointment, BindingResult result) {
        if (result.hasErrors()) {
            return "appointments/new";
        }
        appointmentBook.addAppointment(appointment);
        return "redirect:/appointments";
    }
}

@Controller and AOP Proxying

在某些情况,控制器需要在运行时被AOP代理装饰。比如你直接在控制器中使用@Transactional注解。当遇到这种情况,对于控制器,我们建议使用基于类的代理。对控制器而言,这通常是默认的选择。但是如果一个控制必须要实现一个不是Spring上下文回调的接口(比如,不是InitalizingBean,*Aware等),你可能需要显示的配置基于类的代理。比如,对于<tx:annotation-driven/>而言,要改成<tx:annotation-driven proxy-target-class="true">

New Support Classes for @RequestMapping methods in Spring MVC 3.1

Spring 3.1为@RequestMapping方法引入了一组新的支持类,分别是ReqeustMappingHandlerMappingRequestMappingHandlerAdapter。在Spring 3.1之后,它们被推荐甚至被要求使用。新的支持类会被MVN命名空间和MVC Java配置默认启用,但是如果二者都未使用时,就必须显式的配置。本节讨论新老支持类一些重要的不同点。
在Spring 3.1之前,类型(类)和方法级别的请求会在两个部分被检测——首先控制器会被DefaultAnnotationHandlerMapping选择,之后AnnotataionMethodHandlerAdapter会选择调用的方法。
Spring 3.1提供了新的支持类,因此调用哪个方法处理请求只在RequestMappingHanderMapping中决定。将控制器方法看作不同的端点的集合,端点映射到由类和方法级别的@ReqeustMapping信息派生出的方法。
这也产生了一些新的可能性。HandlerInterceptorHandlerExceptionResolver现在可以预测哪个基于对象的处理作为HandlerMethod,这允许他们减产方法,包括参数和关联的注解。对URL的处理不再需要分为不同的控制器。
但是它也不再支持一些事: 先通过SimpleUrlHandlerMapping或是BeanNameUrlHandlerMapping选择控制器,再由@RequestMapping注解细化方法。
将方法名称作为回调机制来区分两个没有显式区分映射路径但其他方面(比如HTTP方法)相同的方法。在新的支持类中,@RequestMapping的方法必须要映射不同的路径。
* 对控制器中没有其他更具体的方法可以匹配,只有一个默认的(没有显式指定映射的)方法,则用该方法处理请求。在新的支持类中,如果没有匹配的方法,则会抛出404错误。
现有的支持类仍然可以支持上述的特性。但是为了使用Spring MVC 3.1的新特性。你必须要使用新的支持类。

URI Template Patterns

URI模板可以方便的访问@RequestMapping方法中选择的部分。
URI模板是一个URI风格的字符串,包含了一个或多个变量名。当你要替换这些变量时,模板就变成了一个URI。proposed RFC为URI模板定义了URI是如何参数化的。比如,URI模板http://www.example.com/users/{userId}包含了变量userId。http://www.example.com/user/fred为变量设置了fred值。
在Spring MVC你可以在方法参数参数上使用@PathVariable注解来绑定URI模板的变量值:

@GetMapping("/owners/{ownerId}")
public String findOwner(@PathVariable String ownerId, Model model) {
    Owner owner = ownerService.findOwner(ownerId);
    model.addAttribute("owner", owner);
    return "displayOwner";
}

URI模板"/owners/{ownerId}"指定了变量名为ownerId。当控制器处理请求时,URI这部分的值会被设置为ownerId的属性。比如,一个为/owners/fred的请求进来时,ownerId会被设置成fred

为了处理@PathVariable注解,Spring MVC需要找到符合URI模板的变量名称。你一颗在注解中指定它:

@GetMapping("/owners/{ownerId}")
public String findOwner(@PathVariable("ownerId") String theOwner, Model model) {
    // implementation omitted
}

或者如果URI模板的变量名和方法参数的名字匹配,你可以省略指定。只要你的代码在编译时打开了调试信息或是Java 8中的-parameters编译标志,Spring MVC会自动匹配方法中的参数名和URI模板的变量名:

@GetMapping("/owners/{ownerId}")
public String findOwner(@PathVariable String ownerId, Model model) {
    // implementation omitted
}

一个方法可以有任意多个@PathVariable注解:

@GetMapping("/owners/{ownerId}/pets/{petId}")
public String findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
    Owner owner = ownerService.findOwner(ownerId);
    Pet pet = owner.getPet(petId);
    model.addAttribute("pet", pet);
    return "displayPet";
}

@PathVariable注解对Map<String,String>参数使用时,所有的URI模板变量会被填充到map。URI模板可以是类型(类)和方法级别的@RequestMapping注解组装成的。findPet()方法可以被像/owners/42/pets/21这样的URL请求所调用。

@Controller
@RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController {

    @RequestMapping("/pets/{petId}")
    public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
        // implementation omitted
    }

}

@PathVariable注解的参数可以是任何简单的类型,比如int,long,Date等等。Spring会为你自动的转换成合适的类型,如果这么失败了,则会抛出TypeMismatchException。你可以为其他数据类型的解析注册所需的支持。见"Method Parameters And Type Conversion""Customizing WebDataBinder initalization"

URI Template Patterns with Regular Expressions

有时你需要更细粒度的定义URI模板变量。考虑"/spring-web/spring-web-3.0.5.jar"这个URL。你会如何将它分隔成多个部分?
@RequestMapping注解支持用正则表达式表示URI模板变量。以{varName:regex}的形式,第一部分是变量名,而第二部分是正则表达式。比如:

@RequestMapping("/spring-web/{symbolicName:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{extension:\\.[a-z]+}")
public void handle(@PathVariable String version, @PathVariable String extension) {
    // ...
}

Path Patterns

另外,@RequestMapping注解和所有@RequestMapping的变体都支持Ant-风格的路径样式(比如,/myPath/*.do)。同样也支持URI模板变量和Ant-风格样式的组合(比如,/owners/*/pets/{petId})。

Path Pattern Comparison

当一个URL符合多个样式,那么会(对样式)排序以找出最符合的。
更少的URI变量和通配符的样式会被认为更精确。比如,/hotels/{hotel}/*有一个URI变量和一个通配符,它要比有一个URI变量和两个通配符的/hotels/{hotel}/**更为精确。
如果两个样式有相同数量(的URI变量和通配符),那么更长的一个,被认为更精确。比如/hotels/{hotel}/hotels/*来的精确。
还有些额外的特殊规则: 默认的映射/**不如其他样式精确。比如/api/{a}/{b}/{c}更为精确。
诸如/public/**这样的前缀样式不如其他不含有双通配符的样式精确。比如/public/path3/{a}/{b}/{c}更为精确。
详细信息请看AntPathMathcer中的AntPatternComparator。注意可以自定义PathMatcher(见Spring MVC配置那节中的22.16.11,"Path Matching")。

Path Patterns with Placeholders

@ReqeustMapping中的样式支持针对本地属性或是系统属性和环境变量的占位符${...}。这对于需要根据配置在自定义控制器映射路径的情况是很有用的。更多关于占位符的信息,请查看PropertyPlaceholderConfigurer类的文档。

Suffix Pattern Matching

Spring默认为样式提供了后缀".*",因此/person同样隐式的映射了/person.*。这使得通过URL请求不同的资源变得简单。(比如,/persion.pdf/person.xml)。
后缀样式的匹配可以被关闭,也可以被限制成一组明确内容的路径拓展。通常建议将普通请求映射的歧义最小化。比如/person/{id},这里的点不一定就表示文件拓展名,像/person/[email protected]/persion/[email protected]。此外,后缀样式匹配和内容商议都可以在某些情况下被用来恶意攻击,因此要限制它们的意义。
22.16.11节,"Path Matching"了解关于后缀样式匹配的配置和22.16.6节"Content Negotiation",了解关于内容商议的配置。

Suffix Pattern Matching and RFD

2014年,反射文件下载(RFD)攻击首次在Trustwave的论文中被描述。这种攻击和XSS很像,都依赖于将输入(比如,查询参数,URI变量)反射到响应中。但和在HTML中插入JavaScript不同,RFD攻击变成了让浏览器下载并基于拓展名(比如.bat,.cmd),将响应当做可以被双击执行的脚本。
在Spring MVC中,@ResponseBodyResponseEntity方法存在风险,因为它们会根据客户端的请求,包括通过URL的路径拓展名,(将响应)渲染成不同的内容类型。但是请注意,无论是禁用后缀样式匹配还是禁用内容协商的路径拓展名的使用都不能有效的防止RFD攻击。
为了全面防止RFD攻击,在响应体被渲染之前,Spring MVC添加了一个头Content-Dispostion:inline;filename=f.txt来建议一个稳定安全的文件下载名。只有在URL路径包含一个既没有在白名单中也没有显式注册在内容商议中的文件拓展名时,才会这么做。但当浏览器直接输入URL时,这可能有副作用。
许多通用的路径拓展名默认在白名单中,而且REST API通常也不会之家在浏览器中输入URL。尽管如此,使用自定义HttpMessageConverter实现的应用可以显示的未内容协议注册文件拓展名,也不会为这些拓展名田间Content-Disposition头。见22.16.6节,"Content Negotiation"

它最初是作为CVE-2015-5211的一部分。以下是报告的其他建议:
对JSON响应进行编码而不是转义。这也是OWASP XSS的建议。有关如何用Spring实现这点的例子请看spring-jackson-owasp
在配置中关闭后缀样式匹配或是限制为只显示注册的后缀。
将内容协议的"useJaf"和"ignoreUnkownPathExtensions"设置为false,这会在对于未知拓展名的URL请求产生406的响应。但是请注意,对于本身以点结尾的URL来说这不是个好主意。
为响应添加X-Content-Type-Options:nosniff头。Spring Security 4中默认这么做了。

Matrix Variables

URI规范RFC3986定义了路径片段中包含姓名属性值的键值对的可能性。规范中没有规定特定的术语(称呼它)。通常可以叫做"URI路径参数",在Tim Berners-Lee的一份旧的帖子中提出的"Matrix URIs",也经常被使用和广泛的了解。在Spring MVC中,我们用matrix variables的概念。
Matrix variables可以出现在任何路径片段中,每个matrix variable用";"(分号)分隔。比如:"/cars;color=red;year=2012"。多个值可以通过","(逗号)分隔"color=red,green,blue"或者重复使用变量名表示"color=red;color=green;color=blue"
如果URL中会包含matrix variables,那么请求映射样式必须要用URI模板中表示。它确保了请求可以被正确的匹配而不管matrix variables是否存在,或是以何种顺序存在。
下面是提取matrix variable "q"的例子:

// GET /pets/42;q=11;r=22

@GetMapping("/pets/{petId}")
public void findPet(@PathVariable String petId, @MatrixVariable int q) {

    // petId == 42
    // q == 11

}

由于所有的路径片段都可能包含matrix variables,在一些情况下你需要更确切的定义martrix variables出现的位置:

// GET /owners/42;q=11/pets/21;q=22

@GetMapping("/owners/{ownerId}/pets/{petId}")
public void findPet(
        @MatrixVariable(name="q", pathVar="ownerId") int q1,
        @MatrixVariable(name="q", pathVar="petId") int q2) {

    // q1 == 11
    // q2 == 22

}

matrix variable可以被定义为可选的并指定一个默认值:

// GET /pets/42

@GetMapping("/pets/{petId}")
public void findPet(@MatrixVariable(required=false, defaultValue="1") int q) {

    // q == 1

}

可以填充所有的matrix variables到Map中:

// GET /owners/42;q=11;r=12/pets/21;q=22;s=23

@GetMapping("/owners/{ownerId}/pets/{petId}")
public void findPet(
        @MatrixVariable MultiValueMap<String, String> matrixVars,
        @MatrixVariable(pathVar="petId"") MultiValueMap<String, String> petMatrixVars) {

    // matrixVars: ["q" : [11,22], "r" : 12, "s" : 23]
    // petMatrixVars: ["q" : 11, "s" : 23]

}

注意为了使用matrix variables,你必须将ReqeustMappingHandlerMappingremoveSemicolonContent的属性设为false。它默认被设为true

MVC Java配置和MVC命名空间都提供了启用matrix variables的选项。
如果你在使用Java config,Advanced Customization with MVC Java Config这节描述了如何自定义RequestMappingHandlerMapping
对于MVC命名空间,<mvc:annotation-driven>元素有一个enable-matrix-variables的属性应该要被设置true。它默认被设为false

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <mvc:annotation-driven enable-matrix-variables="true"/>

</beans>

Consumable Media Types

你可以通过制定可消化的媒体类型列表来缩小主映射的范围。只有请求头的Content-Type符合制定的媒体类型,请求才算符合。比如:

@PostMapping(path = "/pets", consumes = "application/json")
public void addPet(@RequestBody Pet pet, Model model) {
    // implementation omitted
}

可消化的媒体类型的表达式也可以用!text/plain的形式确定,它匹配所有除了Content-Typetext/plain之外的请求。也可以考虑使用MediaType提供的常量,比如APPLICATION_JSON_VALUEAPPLICATION_JSON_UTF8_VALUE

可消费类型的条件对类和方法级别(的注释)都支持。其他大部分的条件不同,当使用在类级别时,方法级别的可消费类型会覆盖类级别的定义,而不是拓展它。

Producible Media Types

你可以通过指定生成的媒体类型列表来缩小主映射的范围。只有在Accept请求头符合其中一个值时请求才会被匹配。另外,使用生成条件确保响应的实际的内容类型需要遵循生成条件。比如:

@GetMapping(path = "/pets/{petId}", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
@ResponseBody
public Pet getPet(@PathVariable String petId, Model model) {
    // implementation omitted
}

注意,生成条件中的媒体类型中也可以选择指定字符集。比如,上面的代码中我们指定了和MappingJackson2HttpMessageConverter默认配置相同的媒体类型,同时包括了UTF-8字符集。
和consumes一样,生成的媒体类型的表达式也可以用!text/plain这样的形式确定,他匹配了所有所有Accept头不为text/plain的请求。也可以考虑使用MediaType中提供的常量,比如APPLICATION_JSON_VALUEAPPLICATION_JSON_UTF8_VALUE
produces条件也同时支持类和方法界别。不像其他大部分的条件,当使用在类级别,方法级别的生成类型会覆盖它而不是拓展它。

Request Parameters and Header Values

你可以通过像"myParam","!myParam",或是myParam=myValue这样的参数条件还缩小请求匹配的范围。上面前两个表示存在/不存在这样的参数,第三个指定了参数的值。下面是一个关于参数值条件的例子:

@Controller
@RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController {

    @GetMapping(path = "/pets/{petId}", params = "myParam=myValue")
    public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
        // implementation omitted
    }

}

它同样可以用来测试请求头是否存在/不存在或是基于特定的请求头的值:

@Controller
@RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController {

    @GetMapping(path = "/pets", headers = "myHeader=myValue")
    public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
        // implementation omitted
    }

}

尽管你可以在媒体类型中使用通配符来匹配Content-Type或是Accept头(比如,"content-type=text/"会匹配"text/plain"和"text/html"),但是建议使用consumesproduces*时直接指定。它们就是出于这个目的被设计的。

HTTP HEAD and HTTP OPTIONS

映射到"GET"的@RequestMapping方法同样隐式的映射到了"HEAD",因此不需要显式的声明"HEAD"。HTTP HEAD请求会像HTTP GET请求一样被处理,但只有字节数被写入响应体并设置"Content-Length"头。
@RequestMapping方法同样内置了对HTTP OPTIONS的支持。默认的情况下,HTTP OPTIONS请求通过显式在@RequestMapping方法上声明HTTP方法来匹配URL样式,并让请求得以被处理。如果没有显式的设置HTTP方法,那么"GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS"都是允许的。最好总是直接在@ReqeustMapping中声明打算处理的HTTP方法,会是选择使用专用的@RequestMapping变体(见"Composed @RequestMapping Variants"这节)。

尽管没有必要,但可以将@RequestMapping方法映射到并处理HTTP HEAD或HTTP OPTIONS,或两者同时。

22.3.3 Defining @ReqeustMapping handler methods

@RequestMapping处理器方法可以有很领过的签名。支持的方法参数和返回值将在接下来讨论。大部分参数都可以以任意的顺序使用,除了BindingResult参数。这会在霞姐讨论。

Spring 3.1引入了@RequestMapping新的支持类,分别是RequestMappingHandlerMappingRequestMappingHandlerAdapter。它们被建议使用甚至要求使用它们从而使用Spring MVC 3.1之后的新特性。新的支持类会被MVC命名空间和MVC Java配置默认使用,如果没有使用这两者的话需要显式配置。

Supported method argument types

下面是支持的方法参数: (Servlet API)Request或Response对象.可以选择任何具体的request活response类型,比如ServletRequest或是HttpServletRequest
(Servlet API)session对象:HttpSession的类型。这种类型的参数表示对应的Session。因此,这种参数永远不会为null

在特殊的Servlet环境中,对Session的访问可能不是线程安全的。如果多个请求可能同时访问session,那么可以考虑将RequestMappingHandlerAdapter的"synchronizeOnSession"标签设为"true"。

  • org.springframework.web.context.request.WebRequestorg.springframework.web.context.request.NativeWebRequest。它允许通用的请求参数的访问以及request/session属性的访问,而不需要和Servlet/Portlet API耦合。
  • java.util.Locale,表示当前请求的语言环境,由最具体的语言环境解析器(在MVC环境中,被配置成LocaleResolver/LocaleContextResolver)决定。
  • java.util.TimeZone(Java 6+)/java.time.ZoneId(Java 8),表示了当前请求关联的时区,由LocaleContextResolver决定
  • java.io.InputStream/java.io.Reader,用来访问请求的内容,参数值是由Servlet API暴露出的原生InputStream/Reader。
  • java.io.OutputStream/java.io.Reader用来生成响应的内容。参数值是有Servelt API暴露出的原生的OutputStream/Writer。
  • org.springframework.http.HttoMethod用来表示HTTP请求方法。
  • java.security.Principal包含当前授权的用户。
  • @PathVariable注解的参数,表示URI模板变量,见"URI Template Patterns"
  • @MatrixVariable注解的参数,表示URI路径片段中的键值对。见"Matrix Variables"
  • @RequestParam注解的参数表示特定的Servlet请求参数。参数值会被转换成被声明的方法参数的类型。见"Binding reqeust parameters to method parameters with @ReqeustParam"
  • @RequestHeader注解的参数表示特定的Servlet请求的HTTP头。参数值被转成声明的方法参数类型。见"Mapping request header attributes with the @RequestHeader annotation"
  • @RequestBody注解的参数表示HTTP请求体。参数值会被HttpMessageConverter转成被声明的方法参数类型。
  • @ReqeustPart注解的参数用来访问"multipart/form-data"请求部分的内容。见22.10.5,"Handling a file upload request from programmatic clients"22.10,"Spring`s multipart(file upload) support"。
  • SessionAttribute注解访问长久存在的session属性(比如用户授权对象),而不是通过@SessionAttributes将模型的属性暂时存在session,作为控制器流的一部分。
  • ReqeustAttribute注解的参数表示请求的属性。
  • HttpEntity<?>参数可以访问Servlet请求的HTTP头和内容。请求的流会被HttpMessageConverter转到entity body中。见"Using HttpEntity"
  • java.util.Map/org.springframework.ui.Model/org.springframework.ui.ModelMap表示暴露给视图的模型。
  • org.springframework.web.servlet.mvc.support.RedirectAttributes用来指定在重定向时使用的属性集合,并且可以添加flash属性(临时存储在服务器端并让他们在重定向之后仍可用)。见"Passing Data To the Redirect Targt"和22.6节,"Using flash attributes"
  • 命令或表单对象。由可定制的类型转换器,或是依赖于@InitBinder方法和HandlerAdapter配置,将请求参数绑定bean属性上(通过setter方法)或直接绑定到字段。见RequestMappingHandlerAdapterwebBindingInitializer属性,这些命令对象和他的校验结果默认会被作为模型的属性,通过使用命令对象的类名称——比如,模型属性"orderAddress"表示了"some.package.OrderAddress"的命令对象。ModelAttribute注解可以在方法参数中使用来自定义所使用的模型属性名称。
  • org.springframework.validation.Errors/org.springframework.validation.BindingResult,之前的命令或表单对象的校验结果(紧邻的前面一个方法参数)。
  • org.springframework.web.bind.support.SessionStatus,表示表单处理的状态句柄,如果状态已完成,会触发对处理器类级别的由@SessionAttribute注解指定的Session属性的清理。
  • org.springframework.web.util.UriComponentsBuidler,相对于当前请求的主机,端口,scheme,上下文路径和servlet映射的URL builder。

ErrorsBindingResult参数在必须紧跟着被绑定的模型对象,如果方法签名中有多个模型对象,Spring会为每个都创建独立的BindingResult,因此下面的代码是无效的:

@PostMapping
public String processSubmit(@ModelAttribute("pet") Pet pet, Model model, BindingResult result) { ... }

注意,在PetBindingResult之间有个Model参数。为了让代码有用,你需要重新排列一下参数:

@PostMapping
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result, Model model) { ... }

JDK 1.8的java.util.Optional支持作为有reqequired属性注解(比如,@RequestParam,@ReqeustHeader等)标注的方法属性。在这种情况下使用java.util.Optionalrequired=false意思是一致的。

Supported method return types

下面是支持的返回类型: ModelAndView对象,该模型隐式地增加了命令对象以及@ModelAttribute注释的引用结果的数据访问的方法。
Model对象,RequestToViewNameTranslator会隐式的决定视图的名字,并且隐式的增加了命令对象以及@ModelAttribute注释的引用结果的数据访问的方法。
Map对象,表示暴露出的模型,ReqeustToViewTranslator会隐式的决定使用的名字,并且增加了命令对象以及@ModelAttribute注释的引用结果的数据访问的方法。
View对象,通过命令对象和@ModelAttribute注解隐式确定模型。处理器方法也可以通过声明模型参数以编程的方式来丰富模型。
String,表示一个逻辑视图名,伴随着一个由命令对象和@ModelAttribute注解确定的模型来访问数据。处理器方法也可以通过声明模型参数以编程的方式来丰富模型。
void,如果一个方法自己处理响应(直接写响应的内容,为此声明了类型为ServletResponse/HttpServletResponse的参数),或是视图名可以隐式的被ReqeustToViewNameTranslator决定(没有在处理器的方法签名中声明response参数)。
如果方法被@ResponseBody注解,返回值会被写入response HTTP体。返回值会被HttpMessageConverter转换成声明方法的参数类型。见"Mapping the response body with the @ResponseBody annotation"
HttpEntity<?>ResponseEntity<?>对象,来访问Servlet response HTTP头和内容。entity body会被HttpMessageConverter转成response的流。见"Using HttpEntity"
HttpHeaders对象,返回不含有响应体的响应。
Callable<?>,当应用程序希望处理结果有Spring MVC管理的线程异步的产生时使用。
DeferredResult<?>,在应用程序希望以自己选择的线程来产生返回值时使用。
ListenableFuture<?>CompletableFuture<?>/CompletionStage<?>,在应用程序希望返回值由线程池中的一个任务产生时使用。
ResponseBodtEmitter可以异步的返回多个对象到响应中;支持作为ResponseEntity的主体。
SseEmitter可以异步的将Server-Sent事件写入响应;支持作为ResponseEntity的主体。
StreamingResponseBody可以异步的将输出流写入响应中;支持作为ResponseEntity的主体。
任何其他返回类型被视为暴露给视图的单个模型属性,使用在方法级别通过@ModelAttribute指定的属性名称(或基于返回类型类名称的默认属性名称)。模型隐式的增加了命令对象以及@ModelAttribute注释的引用结果的数据访问的方法。

Binding request parameters to method parameters with @RequestParam

使用@ReqeustParam注解乐意在你的控制器中将请求参数绑定到方法的参数中。
下面展示了用法:

@Controller
@RequestMapping("/pets")
@SessionAttributes("pet")
public class EditPetForm {

    // ...

    @GetMapping
    public String setupForm(@RequestParam("petId") int petId, ModelMap model) {
        Pet pet = this.clinic.loadPet(petId);
        model.addAttribute("pet", pet);
        return "petForm";
    }

    // ...

}

默认使用这个注解的参数都是必须的,但是你可以通过设置@RequestParamrequired属性为false将这个参数设为可选。(比如,@ReqeustParam(name="id", required=false))。
如果目标方法的参数如果不是String,那么类型转换会被自动应用。见"Method Parameters And Type Conversion"
Map<String, String>MultiValueMap<String, String>参数使用了@ReqeustParam注解时,map会被请求参数所填充。

Mapping the request body with the @RequestBody annotation

@ReqeustBody表示方法参数和HTTP方法参数请求体绑定。比如:

@PutMapping("/something")
public void handle(@RequestBody String body, Writer writer) throws IOException {
    writer.write(body);
}

请求体被HttpMessageConverter转成方法参数。HttpMessageConverter负责将Http请求转成对象,以及将对象转成Http响应体。ReqeustMappingHandlerAdapter通过下列默认的HttpMessageConverters支持@RequestBody注解: ByteArrayHttpMessageConverter转换字节数组。
StringHttpMessageConverter转换字符串。
FormHttpMessageConverter转成MultiMap
SourceHttpMessageConverter,和javax.xml.transform.Source相互转换。 关于这些转换器的更多信息,见Message Converters。注意,如果使用MVC命名空间或是MVC Java配置,默认会注册更多的message converters。见22.16.1节,"Enabling the MVC Config or the MVC Namespace"获取更多的信息。
如果你想要读或写XML,你需要配置MarshallingHttpMessageConverter,并指定来自org.springframework.oxm包中的MarshallerUnmarshaller实现。下面的列子展示了如何配置,但是如果你的应用使用了MVC命名空间或是MVC Java配置,那么见22.16.1,"Enabling the MVC Java Config or the MVC XML NameSpace")。

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
    <property name="messageConverters">
        <util:list id="beanList">
            <ref bean="stringHttpMessageConverter"/>
            <ref bean="marshallingHttpMessageConverter"/>
        </util:list>
    </property
</bean>

<bean id="stringHttpMessageConverter"
        class="org.springframework.http.converter.StringHttpMessageConverter"/>

<bean id="marshallingHttpMessageConverter"
        class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter">
    <property name="marshaller" ref="castorMarshaller"/>
    <property name="unmarshaller" ref="castorMarshaller"/>
</bean>

<bean id="castorMarshaller" class="org.springframework.oxm.castor.CastorMarshaller"/>

@RequestBody方法参数可以结合@Valid参数使用,这种情况下,配置的Validator实例会检验它。当使用MVC命名空间或是MVC Java配置时,假设类路径上有JSR-303可用的实现,那么JSR-303校验器会被自动的配置。
@ModelAttribute参数一样,Errorsca念书可以用来检查错误。如果没有声明这个参数,那么会抛MethodArgumentNotValidException异常。异常会被DefaultHandlerExceptionResolver,它会抛出400异常到客户端。

同样的,关于在MVC命名空间或是MVC Java配置中配置message converters和validator见22.16.1节,"Enabling the MVC Java Config or the MVC XML Namespace"

Mapping the response body with the @ReqeustBody annotation

@ResponseBody注解和@RequestBody很相似。这个注解用在方法上,表明返回值直接写进HTTP response(不是到Model中,或是作为视图名)。比如:

@GetMapping("/something")
@ResponseBody
public String helloWorld() {
    return "Hello World";
}

上面的例子直接将Hello World文本写进了Http响应流。
对于@RequestBody,Spring通过HttpMessageConverter将返回的对象转换至响应体。更多转换器的信息,见前一节和Message Converters

Creating REST Controllers with the @RestController annotation

用控制器来实现一个只提供JSON,XML或自定义的媒体内容的REST API很常见。为了方便起见,你可以将你的控制器类加上@RestController注释,而不用在你所有的@RequestMapping方法中添加@ResponseBody注释。
@RestController结合了@ResponseBody@Controller。另外,在框架未来的版本中,它可能为你的控制器添加更多的含义和语义。
和其他常规的@Controller一样,@RestController可以由@ControllerAdvice@RestControllerAdvice协助。见"Advising controllers with @ControllerAdvice and @RestControllerAdvice"这节获得更多信息。

Using HttpEntity

HttpEntity@RequestBody@ResponseBody类似。除了可以访问请求体和响应体之外,HttpEntity(和特定于响应的子类ResponseEntity)也允许访问请求头和响应头,像这样:

@RequestMapping("/something")
public ResponseEntity<String> handle(HttpEntity<byte[]> requestEntity) throws UnsupportedEncodingException {
    String requestHeader = requestEntity.getHeaders().getFirst("MyRequestHeader"));
    byte[] requestBody = requestEntity.getBody();

    // do something with request header and body

    HttpHeaders responseHeaders = new HttpHeaders();
    responseHeaders.set("MyResponseHeader", "MyValue");
    return new ResponseEntity<String>("Hello World", responseHeaders, HttpStatus.CREATED);
}

上面的例子中,MyReqeustHeader的请求头中取值,并从请求体重读取字节数组。它将MyResponseHeader添加到响应,将Hello World写入响应流,并将响应状态设置为201(已创建)。
@ReqeustBody@ResponseBody一样,Spring使用HttpMessageConverter来转换请求和响应的流。更多关于转换器的信息,见前一节和Message Converters

Using @ModelAttribute on a method

@ModelAttribute注释可以用在方法或是方法参数上。本节解释它在方法上的用法,下一节解释他在方法参数上的用法。
方法上的@ModelAttribute注解表名这个方法为添加一个或多个model属性。这些方法支持和@RequestMapping方法一样的参数类型,但不能直接映射请求。实现上,一个控制器中的@ModelAttribute方法会在同个控制器的@ReqeustMapping方法调用前调用。一些列子:

// Add one attribute
// The return value of the method is added to the model under the name "account"
// You can customize the name via @ModelAttribute("myAccount")

@ModelAttribute
public Account addAccount(@RequestParam String number) {
    return accountManager.findAccount(number);
}

// Add multiple attributes

@ModelAttribute
public void populateModel(@RequestParam String number, Model model) {
    model.addAttribute(accountManager.findAccount(number));
    // add more ...
}

@ModelAttribute方法通常用来为model添加常用的属性,比如,根据状态或是宠物类型来填充一个下拉菜单,或是检索一个命令对象,比如Account,来表示HTML表单里的数据。后面这种情况会在下一节中讨论。
注意,以上是@ModelAttribute方法的两种方式。第一种,方法将返回值隐式的添加到了属性中。第二种,方法接受了Model参数,通过它可以添加任意数量的模型属性。你可以根据你的西药选择哪种方式。
一个控制器可以有任意数量的@ModelAttribute的方法。它们都在同一个控制器的@ReqeuestMapping方法调用前被调用。
@ModelAttribute方法可以定义在@ControllerAdvice注解的类中,并且方法将被应用到多个控制器。见"Advising controllers with @ControllerAdvice and @RestControllerAdivce"了解更多信息。

如果没有显示指定model的属性名称会怎么样呢?这种情况下,会要根据其类型分配一个默认的属性名称。比如,如果方法返回了Account类型的对象,默认的名字是"account"。你可以通过@ModelAttribute注解的值来指定属性名称。如果是直接添加属性到Model,可以使用addAttribute(..)方法合适的重载形式-比如,带有或不带有属性名的形式。

@ModelAttribute注解也可以用在@RequestMapping方法上。这种情况下,@ReqeustMapping方法的返回值会作为模型的属性而不再是视图的名称。然后,会根据视图名称规定决定视图的名称,这可方法返回了void很像——见22.13.3节,"Default view name"。

Using @ModelAttribute on a method argument

正像前一节所说的,@ModelAttribute可以用在方法和方法参数上。这节将介绍它在方法参数上的用法。
用在方法参数上的@ModelAttribute表示这个参数应该从模型中检索。如果在模型中不存在这个参数,那会初始化这个参数,并添加到模型中。一旦模型中存在,那么这个参数的字段会匹配请求中的同名参数,并被填充。这就是Spring MVC中的数据绑定,这个机制让你不必再解析表单各个字段,从而省去很多工作。

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute Pet pet) { }

上面给出的例子中,Pet实例是从哪来的?下面是一些选项: 由于使用了@SessionAttributes,可能已经存在在模型中——见"Using @SessionAttributes to store model attributes in the HTTP session between reqeust一节"
由于相同控制器中使用了@ModelAttribute方法,它可能已经存在于模型中——见前一节。
由URL模板变量和类型转换检索(之后将会解释)。
会根据它默认的构造器初始化
@ModelAttribute的方法是从数据库检索属性的常用方法,还可以通过@SessionAttributes对其进行存储然后在请求间传递。一些情况下,通过URI模板变量和类型转换器检索属性十分方便:

@PutMapping("/accounts/{account}")
public String save(@ModelAttribute("account") Account account) {
    // ...
}

在这个例子中,模型的属性名(即"account")匹配URI模板变量的名称。如果你注册了Converter<String, Account>,能够将String类型的account值转成Account实例,那么上面的例子不需要@ModelAttribute
下一步是数据绑定。WebDataBinder类会匹配请求参数名称——包括查询字符创参数和表达的域——根据名称转成模型的属性字段。在需要的时候,类型转换器会填充匹配的字段。设计数据绑定和校验的内容在第九章,Validation, Data Binding, and Type Conversion。控制器层面的自定义数据绑定程序在"Customizing WebDataBinder initialization"中介绍。
数据绑定可能会引起注入必须的字段缺失或是类型转换失败的错误。为了监察这些错误,可以在@ModelAttribute参数之后立即跟上BindingResult

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) {

    if (result.hasErrors()) {
        return "petForm";
    }

    // ...

}

通过BindingResult,你可以检查是否有错误,(如果有错误),通常的方式是在相同的表单用Spring的<error>标签渲染。
注意,在某些情况下可能会需要不进行数据绑定。对于这种情况,你可以将Model注入到控制器或是选择使用注解的binding标志:

@ModelAttribute
public AccountForm setUpForm() {
    return new AccountForm();
}

@ModelAttribute
public Account findAccount(@PathVariable String accountId) {
    return accountRepository.findOne(accountId);
}

@PostMapping("update")
public String update(@Valid AccountUpdateForm form, BindingResult result,
        @ModelAttribute(binding=false) Account account) {

    // ...
}

除了数据绑定之外,你还可以调用自定义的校验器,同样传入用来记录数据绑定错误的BindingResult。这允许数据绑定和校验的错误保存在同一个地方,之后向用户反馈:

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) {

    new PetValidator().validate(pet, result);
    if (result.hasErrors()) {
        return "petForm";
    }

    // ...

}

或者你可以通过添加JSR-303的@Valid注解自动校验:

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@Valid @ModelAttribute("pet") Pet pet, BindingResult result) {

    if (result.hasErrors()) {
        return "petForm";
    }

    // ...

}

9.8节,"Spring Validation"第九章,Validation, Data Binding, and Type Conversion了解详情及如何配置和使用校验。

Using @SessionAttributes to store model attributes in the HTTP session between reqeusts

类级别的@SessionAttributes注解声明了特定处理器使用的session属性。它通常会列举出模型属性的名称或是类型,并透明的存储在session或对话存储中,在后续的请求中传递。
下面的例子演示了这个注解的用法,指定了模型属性的名称:

@Controller
@RequestMapping("/editPet.do")
@SessionAttributes("pet")
public class EditPetForm {
    // ...
}

Using @SessionAttribute to access pre-existing global session attributes

如果你需要访问全局管理的(即在控制器之外)预存在的session属性,它有可能存在也可能不存在,请在方法参数上使用@SessionAttribute属性:

@RequestMapping("/")
public String handle(@SessionAttribute User user) {
    // ...
}

对于需要添加或是删除session属性的情况,请考虑将org.springframework.web.context.request.WebRequestjavax.servlet.http.HttpSession注入到控制器方法中。
为了将模型属性作为控制器工作流的一部分短暂存储在session,请考虑使用SessionAttributes,见"Using @SessionAttributes to store model attributes in the HTTP session between reqeusts"

Using @ReqeustAttribute to access reqeust attributes

@SessionAttribute类似,@RequestAttribute注解可以用来访问由过滤器或拦截器创建的预存在的请求属性:

@RequestMapping("/")
public String handle(@RequestAttribute Client client) {
    // ...
}

Woring with "application/x-www-form-urlencoded" data

前面的小节介绍了使用@ModelAttribute来支持浏览器对表单的提交。对于非浏览器的客户端也建议使用这个注解。然而它们在处理HTTP PUT请求时有一个显著的区别。浏览器可以通过HTTP GET或HTTP PUT请求提交表单数据。非浏览器客户端只能通过HTTP PUT请求提交表单数据。这是一个挑战,因为Servlet规范要求ServletRequest.getParameter*()系列方法只支持HTTP POST,而不支持HTTP PUT。
为了支持HTTP PUT和PATCH请求,spring-web模块提供了HttpPutFormContentFilter过滤器,可以在web.xml配置:

<filter>
    <filter-name>httpPutFormFilter</filter-name>
    <filter-class>org.springframework.web.filter.HttpPutFormContentFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>httpPutFormFilter</filter-name>
    <servlet-name>dispatcherServlet</servlet-name>
</filter-mapping>

<servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>

上述过滤器拦截了内容类型为application/x-www-form-urlencoded的HTTP PUT和PATCH请求,从请求体重读取表单数据,并封装ServletRequest,使得ServletRequest.getParameter*()系列方法可用。

由于HttpPutFormContentFilter消耗了请求体,因此不应该为类型为application/x-www-form-urlencoded的PUT或PATCH请求配置其他转换器。这包括@RequestBody MultiValueMap<String, String>HttpEntity<MultiValueMap<String, String>>

@CookieValue注解允许绑定HTTP cookie的值到方法参数中。
考虑下列http请求收到的cookie: JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84
下面的列子展示了如果获取JSESSIONIDcookie的值:

@RequestMapping("/displayHeaderInfo.do")
public void displayHeaderInfo(@CookieValue("JSESSIONID") String cookie) {
    //...
}

如果参数的类型不是String,类型转换器会自动应用。见"Method Parameters And Type Conversion"
这个注解对Servlet和Protlet环境下的处理器方法都支持。

Mapping request header attributes with the @RequestHeader annotation

@ReqeustHeader注解支持将请求头绑定到方法参数。
一个请求头的样本:

Host                    localhost:8080
Accept                  text/html,application/xhtml+xml,application/xml;q=0.9
Accept-Language         fr,en-gb;q=0.7,en;q=0.3
Accept-Encoding         gzip,deflate
Accept-Charset          ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive              300

下面的代码演示了如从请求头中获取Accept-EncodingKeep-Alive的值:

@RequestMapping("/displayHeaderInfo.do")
public void displayHeaderInfo(@RequestHeader("Accept-Encoding") String encoding,
        @RequestHeader("Keep-Alive") long keepAlive) {
    //...
}

如果方法参数不是String类型,那么会自动类型转换。见"Method Parameters And Type Conversion"
@ReqeustHeader注解应用在Map<String, String>,MultiMap<String, String>HttpHeaders参数上时,请求头中所有的值都会被填充到map中。

内置了将逗号分隔的字符串转成数组或集合或其他系统已知的转换类型的支持。比如,@ReqeustHeader("Accept")的参数了能是String,也可能是String[]List<String>

这个注解同时支持Servlet和Portlet环境下的处理器方法。

Method Parameters And Type Conversion

从请求中提取出来的字符串类型的值(包括请求参数,路径变量,请求头和cookie值)会被转换成它们所绑定的方法参数或字段(@ModelAttribute参数中的字段)的类型。如果目标类型不是String,Spring会自动转换成合适的类型。支持所有的简单类型,像int,long,Date等。你也可以通过WebDataBinder自定义转换过程。(见"Customizing WebDataBinder initialization")或是通过FormattingConversionService注册Formatters(见9.6节,"Spring Field Formatting")。

Customizing WebDataBinder initialization

为了通过Spring的WebDataBinder使用PropertyEditors自定义请求参数的绑定,可以在你的控制器内或是在@ControllerAdvice类中增加一个@InitBinder注解的方法,或是提供一个自定义的WebBindingInitializer。见"Advising controllers with @ControllerAdvice and @RestControllerAdvice"了解更多信息。

Customizing data binding with @InitBinder

在控制器方法中添加@InitBinder是你可以在你的控制器类中直接配置数据绑定。@InitBinder标注的方法初始化了WebDataBinder,用来为处理器方法填充命令或是表单对象。
除了command/form对象和对应的校验结果兑现之外,init-binder的方法支持所有@ReqesutMapping方法支持的参数。Init-binder方法不能够有返回值。因此,它们总是被声明为void。通常参数会包含WebDataBinderWebReqeustjava.util.Local的组合,使代码可以注册特定于上下文的编辑器。
下面的例子演示了使用@InitBinder为表单中所有java.util.Date属性配置了CustomDateEditor

@Controller
public class MyFormController {

    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        dateFormat.setLenient(false);
        binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
    }

    // ...
}

在Spring4.2中,你可以考虑选择addCustomFormatter指定Formatter的实现代理PropertyEditor实例。如果你恰好在共享的FormattingConversionService中使用基于Formatter的设置,那么这种方法特别有用,同样的方法可以重复用于控制器特定的绑定规则调整。

@Controller
public class MyFormController {

    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
    }

    // ...
}

Configuring a custom WebBindingInitializer

为了拓展属性绑定初始化过程,你可以提供WebBindInitializer接口的自定义实现,然后为AnnotationMethodHandlerAdapter提供自定义Bean配置来启用它,由此覆盖默认的配置。
下面的例子来自PetClinic应用,展示了WebBindingInitializer接口的自定义实现的配置,org.springframework.samples.petclinic.web.ClinicBindingInitializer,它为一系列的PetClinic控制器配置了所需的PropertyEditors。

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
    <property name="cacheSeconds" value="0"/>
    <property name="webBindingInitializer">
        <bean class="org.springframework.samples.petclinic.web.ClinicBindingInitializer"/>
    </property>
</bean>

@InitBinder方法可以定义在@ControllerAdvice注解的方法中,以便应用到多个匹配的控制器中。这为使用WebBindingInitializer提供了其他选择。见"Advising controllers with @ControllerAdvice and @RestControllerAdvice"了解更多。

Advising controllers with @ControllerAdvice and @RestControllerAdvice

@ControllerAdvice注解是一个组建注解,它允许在类路径下自动检测它的实现类。当使用MVC命名空间或MVC Java配置时,它被自动启用。
@ControllerAdvice注解的类可以包含@ExceptionHandler,@InitBinder,和@ModelAttribute注解的方法,这些方法会应用于所有控制器层次结构中的@ReqeustMapping方法中,而非它们声明的控制器层次结构中。
也可以选择@RestControllerAdvice,它会将@ExceptionHandler方法默认为@ResponseBody
@ControllerAdvice@RestControllerAdvice都可以指定一部分控制器:

// Target all Controllers annotated with @RestController
@ControllerAdvice(annotations = RestController.class)
public class AnnotationAdvice {}

// Target all Controllers within specific packages
@ControllerAdvice("org.example.controllers")
public class BasePackageAdvice {}

// Target all Controllers assignable to specific classes
@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
public class AssignableTypesAdvice {}

查看@ControllerAdvice文档获取更多信息。

Jackson Serialization View Support

有时候将上下文对象过滤并序列化到HTTP响应体中会很有用。为了提供这个功能,Spring MVC内置对[Jacksons Serialization Views](http://wiki.fasterxml.com/JacksonJsonViews)渲染的支持。 为了在@ResponseBody控制器方法或返回ResponseEntity的控制器方法中使用它,只需要添加@JsonView`注解在相应的类上,并指出视图的类或接口:

@RestController
public class UserController {

    @GetMapping("/user")
    @JsonView(User.WithoutPasswordView.class)
    public User getUser() {
        return new User("eric", "7!jd#h23");
    }
}

public class User {

    public interface WithoutPasswordView {};
    public interface WithPasswordView extends WithoutPasswordView {};

    private String username;
    private String password;

    public User() {
    }

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    @JsonView(WithoutPasswordView.class)
    public String getUsername() {
        return this.username;
    }

    @JsonView(WithPasswordView.class)
    public String getPassword() {
        return this.password;
    }
}

注意,尽管@JsonView允许指定多个类,但控制器方法上只允许指定一个类参数。如果你需要使用多个视图,请考虑使用符合接口。

对于依赖视图解析(返回视图名)的控制器方法而言,只需要添加序列化视图类到model中:

@Controller
public class UserController extends AbstractController {

    @GetMapping("/user")
    public String getUser(Model model) {
        model.addAttribute("user", new User("eric", "7!jd#h23"));
        model.addAttribute(JsonView.class.getName(), User.WithoutPasswordView.class);
        return "userView";
    }
}

Jackson JSONP Support

为了@ResponseBodyResponseEntity方法启用对JSONP的支持,声明一个@ControllerAdvice的bean,继承自AbstractJsonpResponseBodyAdvice,像下面这样,控制器方法参数指明了JSONP的查询参数名:

@ControllerAdvice
public class JsonpAdvice extends AbstractJsonpResponseBodyAdvice {

    public JsonpAdvice() {
        super("callback");
    }
}

对于返回视图名的控制器,当请求中含有jsonpcallback的查询参数名称是,JSONP会自动启用。这些名称可以通过jsonpParameterNames属性定义。

22.3.4 Asynchronous Request Processing

Spring MVC 3.2引入了基于Servlet 3的异步请求处理。控制器方法通常不再返回一个值,而是可以返回一个java .util.concurrent.Callable,并通过Spring MVC管理的线程产生返回值。同时,Servlet容器的线程会退出并释放,来允许其他请求。Spring MVC在TaskExecutor的帮助下通过独立的线程调用Callable,当Callable返回,请求会被重新分发回Servlet容器,并继续处理Callable返回的值。下面是一个例子:

@PostMapping
public Callable<String> processUpload(final MultipartFile file) {

    return new Callable<String>() {
        public String call() throws Exception {
            // ...
            return "someView";
        }
    };

}

控制器方法也可以返回一个DeferredResult的实例。这种情况下,返回值可以有任意线程产生,即,不被Spring MVC管理的线程。比如,结果可能由外部一些事件产生,比如JMS message,调度任务等等。下面是这种控制器方法的另一个例子:

@RequestMapping("/quotes")
@ResponseBody
public DeferredResult<String> quotes() {
    DeferredResult<String> deferredResult = new DeferredResult<String>();
    // Save the deferredResult somewhere..
    return deferredResult;
}

// In some other thread...
deferredResult.setResult(data);

理解Servlet 3.0匿名请求处理特性可能有些困难。以下是关于潜在机制的一些基本原理:
ServletReqeust可以通过调用request.startAsync()而进入异步模式。这么做最主要的影响是Servlet,和任何Filter会退出,但是响应仍旧打开等待之后请求处理完整。
调用request.startAsync()会返回AsyncContext用来进一步控制异步处理。比如它提供了dispatch方法,它和Servlet API的转发类似,只是它允许应用程序通过Servlet容器的线程继续处理请求。
* ServletRequest可以访问当前DispatcherType,可以区分初始请求,异步调度,转发和其他调度类型。

了解了上面的内容,下面介绍使用Callable异步处理请求的处理流程:
控制器返回一个Callable
Spring MVC开始异步处理,并将Callable提交到TaskExecutor,让独立的线程处理。
DispatcherServlet和所有的过滤器退出Servlet容器线程,但是响应仍旧打开。
Callable产生结果,Spring MVC将请求分发回Servlet容器继续处理。
* DispatcherServlet再次被调用,继续处理由Callable异步产生的结果。

DefferedResult的处理过程也类似,除了用任意线程来处理异步结果: 控制器返回一个DeferredResult并存储在内存的队列或列表中,以备之后的访问。
Spring MVC开始异步的处理。
DispatcherServlet和所有配置的过滤器退出请求处理的线程,但是响应任然保持打开。
应用程序利用其它线程调度DeferredResult,并且Spring MVC将请求派发回Servlet容器。
* DispatcherServlet再次被调用,继续处理异步产生的结果。

有关异步处理请求的动机或是何时以及为什么的信息,请参考这里的系列博文

Exception Handling for Async Reqeusts

如果控制器返回的一个Callable在执行时引发了异常会怎么办?简单来说它和一个控制器方法中引发异常是同样的。它经历了常规的异常处理机制。更复杂的解释是当一个Callable引发了异常,Spring MVC会将Exception作为结果派发回Servlet容器,然后处理异常的流程不是返回值。当使用DeferredResult时,你可以选择是调用setResult还是setErrortResult并传递Exception实例。

Interception Async Reqeusts

HandlerInterceptor也可以实现AsyncHandlerInterceptor来实现afterConcurrentHandlingStarted回调,这个方法会在异步处理开始时调用,而不是在postHandle或是afterCompletion时。
HandlerInterceptor也可以注册CallableProcessingInterceptor或是DeferredResultProcessingInterceptor来集成更深层的异步请求的生命周期,比如处理一个超时事件。见AsyncHandlerInterceptor的文档获取更多信息。
DeferredResult类型也提供了像onTimeout(Runnable)onCompletion(Runnable)之类的方法。见DeferredResult的文档获取更多信息。
当使用Callable时,你可以通过WebAsyncTask的实例包装它,它提供了超时和完成的注册方法。

HTTP Streaming

控制器方法可以使用DeferredResultCallable来异步的产生返回值,可以利用它实现长轮询的技术,使得服务器可以尽快推送数据到客户端。
如果你想将多个事件推动到同一个HTTP响应中会怎么样?这被称为"Long Polling",也叫作"HTTP Streaming"。Spring MVC通过返回类型为ResponseBodyEmitter的值实现了这点,这种类型可以被用来发送多个对象,而不是像普通的@ResponseBody那样只能有一个对象,每个对象通过HttpMessageConverter被写入到响应。
下面是一个例子:

@RequestMapping("/events")
public ResponseBodyEmitter handle() {
    ResponseBodyEmitter emitter = new ResponseBodyEmitter();
    // Save the emitter somewhere..
    return emitter;
}

// In some other thread
emitter.send("Hello once");

// and again later on
emitter.send("Hello again");

// and done at some point
emitter.complete();

注意,ResponseBodyEmitter也可以作为ResponseEntity的主体来自定义响应的状态和头。

HTTP Streaming With Server-Sent Events

SseEmitterResponseBodyEmitter的子类,提供了对Server-Sent Events的支持。Servet-sent事件是"HTTP Streaming"技术的一种变体,它从服务器推送的事件要符合W3C Server-Sent Event规范。
Server-Sent事件可以在其他用途下使用,比如从服务器推送时间到客户端。在Spring MVC中做到这点很简单,只需要将返回值改成SseEmitter类型。
注意,IE不支持Server-Sent时间,对于更高级的web应用,比如在线游戏,协作,财务应用等,最好考虑Spring的WebSocket,包括SockJS-风格的WebSocket模拟消息推送至支持大部分的浏览器(包括IE),以及更高级的通过发布订阅模型实现的与客户端的交流。更多消息见博文

HTTP Streaming Directly To The OutputStream

ResponseBodyEmitter允许通过HttpMessageConverter将对象写入响应。通常的情况下都是这样,比如写入JSON数据。但是有时候绕过消息转换,直接写入到响应的OutputStream会很有用,比如文件下载。这个通过返回类型为StreamingResponseBody的对象实现。
下面是一个例子:

@RequestMapping("/download")
public StreamingResponseBody handle() {
    return new StreamingResponseBody() {
        @Override
        public void writeTo(OutputStream outputStream) throws IOException {
            // write...
        }
    };
}

注意StreamingResponseBody可以作为ResponseEntity的主体来自定义响应的状态和头。

Configuring Asynchronous Request Processing

Servlet Container Configuration

通过web.xml配置的项目要确认更新的3.0的版本:

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            http://java.sun.com/xml/ns/javaee
            http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    version="3.0">

    ...

</web-app>

通过开启web.xmlDispatcherServlet的子元素<async-supported>true</async-supported>实现对异步的处理。另外参与异步处理的过滤器必须要被配置成指出ASYNC dispatcher的类型。为Spring Framework提供的所有过滤器启用ASYNC dispatcher类型应该是安全的,因为它们通常会扩展OncePerRequestFilter,并且运行时检查过滤器是否需要参与异步分派。
下面是web.xml配置的实例:

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
            http://java.sun.com/xml/ns/javaee
            http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    version="3.0">

    <filter>
        <filter-name>Spring OpenEntityManagerInViewFilter</filter-name>
        <filter-class>org.springframework.~.OpenEntityManagerInViewFilter</filter-class>
        <async-supported>true</async-supported>
    </filter>

    <filter-mapping>
        <filter-name>Spring OpenEntityManagerInViewFilter</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>ASYNC</dispatcher>
    </filter-mapping>

</web-app>

如果使用Servlet 3和基于Java的配置比如通过WebApplicationInitializer,你也需要像web。xml这样设置"asyncSupported"标识和ASYNC dispatcher类型。为了简化这些配置,你可以继承AbstractDispatcherServletInitializer,或者更好的是继承AbstractAnnotationConfigDispatcherServletInitializer,它会自动设置这些选项,并让注册Filter实例变得简单。

Spring MVC Configuration

MVC Java配置和MVC命名空间提供了异步请求处理的选项。WebMvcConfigurer有一个configureAsyncSupport的方法,<mvc:annotation-driven>也有<async-support>的子元素。
这些都允许你配置默认异步请求的超时时间,如果没有设置的话就取决于Servlet容器(比如,Tomcat是10秒)。你可以配置一个AsyncTaskExecutor用来执行从控制器方法返回的Callable实例。强烈建议配置这个属性,因为Spring MVC默认使用了SimpleAsyncTaskExecutor。MVC Java配置和MVC命名空间同样允许你注册CallableProcessingInterceptorDefereedResultProcessingInterceptor实例。
如果你想为某个特定的DefferedResult覆盖默认的超时时间,你可以选择合适的构造器来实现。类似的,对于Callable,你可以使用合适改造函数将它包装成WebAsyncTask来指定超时时间。WebAsyncTask的累构造器也允许提供AsyncTaskExecutor

22.3.5 Testing Controllers

spring-test模块提供了测试被注解的控制器的支持。见15.6节,"Spring MVC Test Framework"